require 'openssl'
require 'digest/sha1'
require 'base64'
require 'cgi'
require 'net/https'
require 'time'
require 'uri'
require 'rexml/document'

$KCODE = 'u' # Enable Unicode support

module AWS
	attr_accessor :aws_access_key, :aws_secret_key, :secure_http, :debug
	attr_reader :time_offset

	def initialize(debug=false)
		@aws_access_key=ENV['EC2_ACCESS_KEY']
		@aws_secret_key=ENV['EC2_SECRET_KEY']
		@time_offset=0
		@secure_http=true
		@debug=debug
	end

	def generate_signature(request_description)
        digest_generator = OpenSSL::Digest::Digest.new('sha1')
        digest = OpenSSL::HMAC.digest(digest_generator, @aws_secret_key, request_description)
        return encode_base64(digest)
    end

    def encode_base64(data)
        return nil unless data
        b64 = Base64.encode64(data)
        return b64.gsub("\n", "")
    end

#	def do_rest(method, uri, data=nil, headers={})
#		signature = generate_rest_signature(method, uri, headers)
#		headers['Authorization'] = "AWS #{@aws_access_key}:#{signature}"
#		headers['Host'] = uri.host
#		headers['Expect'] = '100-continue' if method == 'PUT'
#
#		redirect_count=0
#		while redirect_count < 5
#			Net::HTTP.version_1_1
#			http = Net::HTTP.new(uri.host, uri.port)
#			if @secure_http
#				http.use_ssl = true
#				http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#			end
#
#			debug_request(method, uri, headers, {}, data) if @debug
#
#			if method == 'PUT'
#				puts "Method is PUT: will handle later\n"
#			elsif method == 'GET' and block_given?
#				http.request_get(uri.request_uri, headers) do |response|
#					response.read_body { |segment| yield(segment) }
#				end
#			else
#				response = http.send_request(method, uri.request_uri, data, headers)
#			end # code PUT request handler
#
#			debug_request(response) if @debug
#
#			if response.is_a?(NET::HTTPTemporaryRedirect)
#				uri = URI.parse(response.header['location'])
#				redirect_count += 1
#			elsif response.is_a?(NET::HTTPSuccess)
#				return response
#			else
#				raise ServiceError.new(response)
#			end
#		end # end of while loop
#	end # end of do_rest method

	def generate_rest_signature(method, uri, headers)
		headers['Date'] = current_time.httpdate unless headers['Date']

		req_desc =
          "#{method}\n" +
          "#{headers['Content-MD5']}\n" +
          "#{headers['Content-Type']}\n" +
          "#{headers['Date']}\n"

		amz_headers = headers.reject {|name, value| name.index('x-amz') != 0}
		amz_headers = amz_headers.sort {|x, y| x[0] <=> y[0]}
		amz_headers.each {|name, value| req_desc << "#{name.downcase}:#{value}\n"}

		path = ''

		if not ['s3.amazonaws.com', 'queue.amazonaws.com'].include?(uri.host)
			if uri.host =~ /(.*).s3.amazonaws.com/
				path << '/' + $1
			else
				path << '/' + uri.host
			end

			path << '/' if uri.path == ''
		end

		path << uri.path

		if path == ''
			req_desc << '/'
		else
			req_desc << path
		end

		if uri.query
			uri.query.split('&').each do |param|
				if ['acl', 'torrent', 'logging', 'location'].include?(param)
					req_desc << "?" + param
				end
			end
		end

		if @debug
			puts "REQUEST DESCRIPTION\n=========================="
			puts "#{req_desc.gsub("\n", "\\n\n")}\n\n"
		end

		return generate_signature(req_desc)
	end # end of generate_rest_signature

	def current_time
		if @time_offset
			return Time.now + @time_offset
		else
			return Time.now
		end
	end

    def build_query_params(api_ver, sig_ver, params, indexed_params={}, indexed_start=1)
		built_params = {
			'Version' => api_ver,
			'SignatureVersion' => sig_ver,
			'AWSAccessKeyId' => @aws_access_key
		}

		if params['Timestamp'].nil? and params['Expires'].nil?
			params['Timestamp'] = current_time.getutc.iso8601
		end

		built_params.merge!(params.reject { |name, value| value.nil? })

		indexed_params.each do |param_name, value_array|
			index_count = indexed_start
			value_array.each do |value|
				built_params["#{param_name}.#{index_count}"] = value
				index_count += 1
			end if value_array
		end

		return built_params
	end # end of build_query_params
	
	def do_query(method, uri, params)
		req_desc = params.sort {|x, y| x[0].downcase <=> y[0].downcase}.to_s
		signature = generate_signature(req_desc)
		params['Signature'] = signature
		
		case method
		when 'GET'
			uri.query = ''
			params.each do |name, value|
				uri.query << "#{name}=#{CGI::escape(value.to_s)}&"
			end
			req = Net::HTTP::Get.new(uri.request_uri)
		when 'POST'
			req = Net::HTTP::Post.new(uri.request_uri)
			req.set_form_data(params)
		else
			raise "Invalid HTTP Query method: #{method}"
		end

		Net::HTTP.version_1_1
		http = Net::HTTP.new(uri.host, uri.port)
		if @secure_http
#			http.use_ssl = true
			http.verify_mode = OpenSSL::SSL::VERIFY_NONE
		end

		debug_request(method, uri, {}, params) if @debug

		response = http.request(req)
		
		debug_response(response) if @debug
	
		if response.is_a?(Net::HTTPSuccess)
			return response
		else
			raise ServiceError.new(response)
		end
	end # end of do_query method

	def adjust_time(uri = URI.parse('http://aws.amazon.com/'))
		http = Net::HTTP.new(uri.host, uri.port)
		response = http.send_request('GET', uri.request_uri)
		
		local_time = Time.now
		aws_time = Time.httpdate(response.headers['Date'])
		@time_offset = aws_time - local_time
		
		puts "Time offset for AWS requests: #{@time_offset} seconds" if @debug

		return @time_offset
    end

	def debug_request(method, uri, headers={}, query_params={}, data=nil)
		puts 'REQUEST\n======'
		puts "Method : #{method}"

		params = uri.to_s.split('&')
		puts " URI:  #{params.first}"
		params[1..-1].each { |p| puts "\t &#{p}" } if params.length > 1

		if headers.length > 0
			puts "Headers: "
			headers.each { |n,v|  puts "   #{n}=#{v}" }
		end

		if query_params.length > 0
			puts "Query Parameters"
			query_params.each { |n, v| puts " #{n}=#{v}" }
		end

		if data
			puts "Request body data:"
			if headers['Content-Type'] == 'application/xml'
				REXML::Document.new(data).write($stdout, 2)
			else
				puts data
			end
			data.rewind if data.respond_to?(:stat)
			puts
		end
	end # end of debug_request method
	
	def debug_response(response)
		puts "\nRESPONSE\n======"
		puts "Status: #{response.code} #{response.message}"

		if response.header.length > 0
			puts "Headers: "
			response.header.each { |n, v| puts " #{n}=#{v}" }
		end

		if response.body and response.body.respond_to?(:length)
			puts "Body:"

			if response.body.index('<?xml') == 0
				REXML::Document.new(response.body).write($stdout, 2)
			else
				puts response.body
			end
		end
		puts
	end # end of debug_response method

	class ServiceError < RuntimeError
		attr_accessor :response, :aws_error_xml

		def initialize(http_response)
			response = http_response
			message = "HTTP Error: #{response.code} - #{response.message}"

			if response.body and response.body.index('<?xml')
				aws_error_xml = REXML::Document.new(response.body)

				aws_error_code = aws_error_xml.elements['//Code'].text
				aws_error_message = aws_error_xml.elements['//Message'].text

				message += ", AWS Error: #{aws_error_code} - #{aws_error_message}"
			end

			super(message)
		end

	end

	## Further classes and methods will go here ##
end
